/*-------------------------------------------------------------------------
 *
 * execProcnode.c
 *	 包含调度函数，用于调用适当的“初始化”、“获取元组”和“清理”例程，以处理给定节点类型。
 *	 如果该节点有子节点，则它将假定调用ExecInitNode、ExecProcNode或ExecEndNode对其子节点进行
 *	 处理。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execProcnode.c
 *
 *-------------------------------------------------------------------------
 */
/*
 *	 注释
 *		这曾经是三个文件。现在合并为一个文件，
 *		以便在添加新节点时更容易保持调度例程的同步。
 *
 *	 示例
 *		假设我们想知道鞋类部门经理的年龄和
 *		该部门的员工人数。因此我们有以下查询：
 *
 *				select DEPT.no_emps, EMP.age
 *				from DEPT, EMP
 *				where EMP.name = DEPT.mgr and
 *					  DEPT.name = "shoe"
 *
 *		假设规划器给我们以下计划：
 *
 *						嵌套循环（DEPT.mgr = EMP.name）
 *						/		\
 *					   /		 \
 *				   Seq Scan		Seq Scan
 *					DEPT		  EMP
 *				(name = "shoe")
 *
 *		首先调用 ExecutorStart()。
 *		它调用 InitPlan()，然后在
 *		计划的根节点上调用 ExecInitNode() -- 嵌套循环节点。
 *
 *	  * ExecInitNode() 注意到它正在查看一个嵌套循环，
 *		正如下面的代码所示，它调用 ExecInitNestLoop()。
 *		最终，这会在右侧和左侧子计划上调用 ExecInitNode()，
 *		依此类推，直到整个计划初始化完成。ExecInitNode() 的结果
 *		是构建了与基础计划树具有相同结构的计划状态树。
 *
 *	  * 然后，当调用 ExecutorRun() 时，它调用 ExecutePlan()，
 *		该函数重复调用计划状态树的顶部节点上的 ExecProcNode()。
 *		每次发生此情况时，ExecProcNode() 最终会调用
 *		ExecNestLoop()，接着在其子计划上调用 ExecProcNode()。
 *		这些子计划都是顺序扫描，因此调用 ExecSeqScan()。
 *		ExecSeqScan() 返回的槽可能包含
 *		ExecNestLoop() 用于形成返回元组的属性。
 *
 *	  * 最终，ExecSeqScan() 停止返回元组，嵌套循环连接结束。
 *		最后，ExecutorEnd() 调用 ExecEndNode()，该函数
 *		调用 ExecEndNestLoop()，接着在其子计划上调用 ExecEndNode()，
 *		并导致 ExecEndSeqScan()。
 *
 *		这应该展示了执行器的工作方式，通过
 *		ExecInitNode()、ExecProcNode() 和 ExecEndNode() 将
 *		它们的工作调度到适当的节点支持例程，
 *		这些例程可能会反过来在它们的子计划上调用这些例程。
 */
#include "postgres.h"

#include "executor/executor.h"
#include "executor/nodeAgg.h"
#include "executor/nodeAppend.h"
#include "executor/nodeBitmapAnd.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGather.h"
#include "executor/nodeGatherMerge.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
#include "executor/nodeIncrementalSort.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
#include "executor/nodeMemoize.h"
#include "executor/nodeMergeAppend.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
#include "executor/nodeNamedtuplestorescan.h"
#include "executor/nodeNestloop.h"
#include "executor/nodeProjectSet.h"
#include "executor/nodeRecursiveunion.h"
#include "executor/nodeResult.h"
#include "executor/nodeSamplescan.h"
#include "executor/nodeSeqscan.h"
#include "executor/nodeSetOp.h"
#include "executor/nodeSort.h"
#include "executor/nodeSubplan.h"
#include "executor/nodeSubqueryscan.h"
#include "executor/nodeTableFuncscan.h"
#include "executor/nodeTidrangescan.h"
#include "executor/nodeTidscan.h"
#include "executor/nodeUnique.h"
#include "executor/nodeValuesscan.h"
#include "executor/nodeWindowAgg.h"
#include "executor/nodeWorktablescan.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"

static TupleTableSlot *fc_ExecProcNodeFirst(PlanState *fc_node);
static TupleTableSlot *fc_ExecProcNodeInstr(PlanState *fc_node);
static bool fc_ExecShutdownNode_walker(PlanState *fc_node, void *fc_context);


/* ------------------------------------------------------------------------
 *		ExecInitNode
 *
 *		递归初始化以 'node' 为根的计划树中的所有节点。
 *
 *		输入：
 *		  'node' 是由查询规划器生成的计划的当前节点。
 *		  'estate' 是计划树的共享执行状态。
 *		  'eflags' 是 executor.h 中描述的标志位的按位或。
 *
 *		返回与给定计划节点对应的 PlanState 节点。
 * ------------------------------------------------------------------------
 */
PlanState *
ExecInitNode(Plan *fc_node, EState *fc_estate, int fc_eflags)
{
	PlanState  *fc_result;
	List	   *fc_subps;
	ListCell   *fc_l;

	/*
	 * 到达树叶的末端时什么也不做。
	 */
	if (fc_node == NULL)
		return NULL;

	/*
	 * 确保堆栈中有足够的可用空间。除了在初始化节点树时
	 * 需要在 ExecProcNode()（通过 ExecProcNodeFirst()）中检查这里，
	 * 以确保堆栈不会溢出。
	 */
	check_stack_depth();

	switch (nodeTag(fc_node))
	{
			/*
			 * 控制节点
			 */
		case T_Result:
			fc_result = (PlanState *) ExecInitResult((Result *) fc_node,
												  fc_estate, fc_eflags);
			break;

		case T_ProjectSet:
			fc_result = (PlanState *) ExecInitProjectSet((ProjectSet *) fc_node,
													  fc_estate, fc_eflags);
			break;

		case T_ModifyTable:
			fc_result = (PlanState *) ExecInitModifyTable((ModifyTable *) fc_node,
													   fc_estate, fc_eflags);
			break;

		case T_Append:
			fc_result = (PlanState *) ExecInitAppend((Append *) fc_node,
												  fc_estate, fc_eflags);
			break;

		case T_MergeAppend:
			fc_result = (PlanState *) ExecInitMergeAppend((MergeAppend *) fc_node,
													   fc_estate, fc_eflags);
			break;

		case T_RecursiveUnion:
			fc_result = (PlanState *) ExecInitRecursiveUnion((RecursiveUnion *) fc_node,
														  fc_estate, fc_eflags);
			break;

		case T_BitmapAnd:
			fc_result = (PlanState *) ExecInitBitmapAnd((BitmapAnd *) fc_node,
													 fc_estate, fc_eflags);
			break;

		case T_BitmapOr:
			fc_result = (PlanState *) ExecInitBitmapOr((BitmapOr *) fc_node,
													fc_estate, fc_eflags);
			break;

			/*
			 * 扫描节点
			 */
		case T_SeqScan:
			fc_result = (PlanState *) ExecInitSeqScan((SeqScan *) fc_node,
												   fc_estate, fc_eflags);
			break;

		case T_SampleScan:
			fc_result = (PlanState *) ExecInitSampleScan((SampleScan *) fc_node,
													  fc_estate, fc_eflags);
			break;

		case T_IndexScan:
			fc_result = (PlanState *) ExecInitIndexScan((IndexScan *) fc_node,
													 fc_estate, fc_eflags);
			break;

		case T_IndexOnlyScan:
			fc_result = (PlanState *) ExecInitIndexOnlyScan((IndexOnlyScan *) fc_node,
														 fc_estate, fc_eflags);
			break;

		case T_BitmapIndexScan:
			fc_result = (PlanState *) ExecInitBitmapIndexScan((BitmapIndexScan *) fc_node,
														   fc_estate, fc_eflags);
			break;

		case T_BitmapHeapScan:
			fc_result = (PlanState *) ExecInitBitmapHeapScan((BitmapHeapScan *) fc_node,
														  fc_estate, fc_eflags);
			break;

		case T_TidScan:
			fc_result = (PlanState *) ExecInitTidScan((TidScan *) fc_node,
												   fc_estate, fc_eflags);
			break;

		case T_TidRangeScan:
			fc_result = (PlanState *) ExecInitTidRangeScan((TidRangeScan *) fc_node,
														fc_estate, fc_eflags);
			break;

		case T_SubqueryScan:
			fc_result = (PlanState *) ExecInitSubqueryScan((SubqueryScan *) fc_node,
														fc_estate, fc_eflags);
			break;

		case T_FunctionScan:
			fc_result = (PlanState *) ExecInitFunctionScan((FunctionScan *) fc_node,
														fc_estate, fc_eflags);
			break;

		case T_TableFuncScan:
			fc_result = (PlanState *) ExecInitTableFuncScan((TableFuncScan *) fc_node,
														 fc_estate, fc_eflags);
			break;

		case T_ValuesScan:
			fc_result = (PlanState *) ExecInitValuesScan((ValuesScan *) fc_node,
													  fc_estate, fc_eflags);
			break;

		case T_CteScan:
			fc_result = (PlanState *) ExecInitCteScan((CteScan *) fc_node,
												   fc_estate, fc_eflags);
			break;

		case T_NamedTuplestoreScan:
			fc_result = (PlanState *) ExecInitNamedTuplestoreScan((NamedTuplestoreScan *) fc_node,
															   fc_estate, fc_eflags);
			break;

		case T_WorkTableScan:
			fc_result = (PlanState *) ExecInitWorkTableScan((WorkTableScan *) fc_node,
														 fc_estate, fc_eflags);
			break;

		case T_ForeignScan:
			fc_result = (PlanState *) ExecInitForeignScan((ForeignScan *) fc_node,
													   fc_estate, fc_eflags);
			break;

		case T_CustomScan:
			fc_result = (PlanState *) ExecInitCustomScan((CustomScan *) fc_node,
													  fc_estate, fc_eflags);
			break;

			/*
			 * 连接节点
			 */
		case T_NestLoop:
			fc_result = (PlanState *) ExecInitNestLoop((NestLoop *) fc_node,
													fc_estate, fc_eflags);
			break;

		case T_MergeJoin:
			fc_result = (PlanState *) ExecInitMergeJoin((MergeJoin *) fc_node,
													 fc_estate, fc_eflags);
			break;

		case T_HashJoin:
			fc_result = (PlanState *) ExecInitHashJoin((HashJoin *) fc_node,
													fc_estate, fc_eflags);
			break;

			/*
			 * 材料化节点
			 */
		case T_Material:
			fc_result = (PlanState *) ExecInitMaterial((Material *) fc_node,
													fc_estate, fc_eflags);
			break;

		case T_Sort:
			fc_result = (PlanState *) ExecInitSort((Sort *) fc_node,
												fc_estate, fc_eflags);
			break;

		case T_IncrementalSort:
			fc_result = (PlanState *) ExecInitIncrementalSort((IncrementalSort *) fc_node,
														   fc_estate, fc_eflags);
			break;

		case T_Memoize:
			fc_result = (PlanState *) ExecInitMemoize((Memoize *) fc_node, fc_estate,
												   fc_eflags);
			break;

		case T_Group:
			fc_result = (PlanState *) ExecInitGroup((Group *) fc_node,
												 fc_estate, fc_eflags);
			break;

		case T_Agg:
			fc_result = (PlanState *) ExecInitAgg((Agg *) fc_node,
											   fc_estate, fc_eflags);
			break;

		case T_WindowAgg:
			fc_result = (PlanState *) ExecInitWindowAgg((WindowAgg *) fc_node,
													 fc_estate, fc_eflags);
			break;

		case T_Unique:
			fc_result = (PlanState *) ExecInitUnique((Unique *) fc_node,
												  fc_estate, fc_eflags);
			break;

		case T_Gather:
			fc_result = (PlanState *) ExecInitGather((Gather *) fc_node,
												  fc_estate, fc_eflags);
			break;

		case T_GatherMerge:
			fc_result = (PlanState *) ExecInitGatherMerge((GatherMerge *) fc_node,
													   fc_estate, fc_eflags);
			break;

		case T_Hash:
			fc_result = (PlanState *) ExecInitHash((Hash *) fc_node,
												fc_estate, fc_eflags);
			break;

		case T_SetOp:
			fc_result = (PlanState *) ExecInitSetOp((SetOp *) fc_node,
												 fc_estate, fc_eflags);
			break;

		case T_LockRows:
			fc_result = (PlanState *) ExecInitLockRows((LockRows *) fc_node,
													fc_estate, fc_eflags);
			break;

		case T_Limit:
			fc_result = (PlanState *) ExecInitLimit((Limit *) fc_node,
												 fc_estate, fc_eflags);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			fc_result = NULL;		/* 保持编译器安静 */
	#ifdef FDD	// cppcheck(warning:nullPointer) CWE-476 可能的空指针解引用：结果
			return fc_result;
	#else
			break;
	#endif
	}

	ExecSetExecProcNode(fc_result, fc_result->ExecProcNode);

	/*
	 * 初始化此节点中存在的任何 initPlans。规划器将它们放在
	 * 一个单独的列表中供我们使用。
	 */
	fc_subps = NIL;
	foreach(fc_l, fc_node->initPlan)
	{
		SubPlan    *fc_subplan = (SubPlan *) lfirst(fc_l);
		SubPlanState *fc_sstate;

		Assert(IsA(fc_subplan, SubPlan));
		fc_sstate = ExecInitSubPlan(fc_subplan, fc_result);
		fc_subps = lappend(fc_subps, fc_sstate);
	}
	fc_result->initPlan = fc_subps;

	/* 如果请求，为此节点设置仪器。 */
	if (fc_estate->es_instrument)
		fc_result->instrument = InstrAlloc(1, fc_estate->es_instrument,
										fc_result->async_capable);

	return fc_result;
}


/*
 * 如果一个节点希望在 ExecInitNode() 完成后更改其 ExecProcNode 函数，
 * 它应该使用此函数。这样，任何包装函数都可以重装，
 * 而无需节点知道这如何工作。
 */
void ExecSetExecProcNode(PlanState *fc_node, ExecProcNodeMtd fc_function)
{
	/*
	 * 在 ExecProcNode 回调周围添加一个包装，以在第一次执行时检查堆栈深度，
	 * 并可能添加一个仪器包装。当回调在执行已经开始后被更改时，
	 * 这意味着我们会冗余地执行 ExecProcNodeFirst，但这似乎没问题。
	 */
	fc_node->ExecProcNodeReal = fc_function;
	fc_node->ExecProcNode = fc_ExecProcNodeFirst;
}



/*
 * ExecProcNode 包装器，执行一些一次性的检查，然后调用
 * 相关的节点方法（可能通过一个仪表监控包装器）。
 */
static TupleTableSlot * fc_ExecProcNodeFirst(PlanState *fc_node)
{
	/*
	 * 在节点第一次执行时进行堆栈深度检查。我们
	 * 只在第一次时这样做，因为这在某些常见架构上（例如 x86）
	 * 代价不便宜。这个假设依赖于
	 * ExecProcNode 对给定计划节点的调用将始终在
	 * 大致相同的堆栈深度进行。
	 */
	check_stack_depth();

	/*
	 * 如果需要仪表监控，改变包装器为仅执行
	 * 仪表监控的那种。否则，我们可以省去所有包装器，让
	 * ExecProcNode() 直接调用相关功能。
	 */
	if (fc_node->instrument)
		fc_node->ExecProcNode = fc_ExecProcNodeInstr;
	else
		fc_node->ExecProcNode = fc_node->ExecProcNodeReal;

	return fc_node->ExecProcNode(fc_node);
}


/*
 * ExecProcNode 包装器，执行仪表监控调用。通过将
 * 其保持为单独的函数，我们避免在正常情况下
 * 进行不必要的开销。
 */
static TupleTableSlot * fc_ExecProcNodeInstr(PlanState *fc_node)
{
	TupleTableSlot *fc_result;

	InstrStartNode(fc_node->instrument);

	fc_result = fc_node->ExecProcNodeReal(fc_node);

	InstrStopNode(fc_node->instrument, TupIsNull(fc_result) ? 0.0 : 1.0);

	return fc_result;
}


/* ----------------------------------------------------------------
 *		MultiExecProcNode
 *
 *		执行一个不返回单个元组的节点
 *		（它可能返回一个哈希表、位图等）。调用者应该
 *		检查返回的节点类型是否符合预期。
 *
 * 这基本上与 ExecProcNode 的责任相同，
 * 但它不执行 InstrStartNode/InstrStopNode（主要是因为
 * 它无法确定返回的元组数量）。每个节点的
 * 函数必须提供其自己的仪表监控支持。
 * ----------------------------------------------------------------
 */
Node * MultiExecProcNode(PlanState *fc_node)
{
	Node	   *fc_result;

	check_stack_depth();

	CHECK_FOR_INTERRUPTS();

	if (fc_node->chgParam != NULL) /* 有所变化 */
		ExecReScan(fc_node);		/* 让 ReScan 处理这个 */

	switch (nodeTag(fc_node))
	{
			/*
			 * 仅列出实际支持多执行的节点类型
			 */

		case T_HashState:
			fc_result = MultiExecHash((HashState *) fc_node);
			break;

		case T_BitmapIndexScanState:
			fc_result = MultiExecBitmapIndexScan((BitmapIndexScanState *) fc_node);
			break;

		case T_BitmapAndState:
			fc_result = MultiExecBitmapAnd((BitmapAndState *) fc_node);
			break;

		case T_BitmapOrState:
			fc_result = MultiExecBitmapOr((BitmapOrState *) fc_node);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			fc_result = NULL;
			break;
	}

	return fc_result;
}


/* ----------------------------------------------------------------
 *		ExecEndNode
 *
 *		递归清理以'node'为根的计划中的所有节点。
 *
 *		在此操作后，查询计划将无法进一步处理。
 *		这个函数只应在查询计划完全执行后调用。
 * ----------------------------------------------------------------
 */
void ExecEndNode(PlanState *fc_node)
{
	/*
	 * 到达树叶的末端时什么也不做。
	 */
	if (fc_node == NULL)
		return;

	/*
	 * 确保堆栈中有足够的空间。在 ExecProcNode()
	 * （通过 ExecProcNodeFirst()）之外需要在这里检查，因为
	 * 并不能保证 ExecProcNode() 会在所有节点上被调用。
	 */
	check_stack_depth();

	if (fc_node->chgParam != NULL)
	{
		bms_free(fc_node->chgParam);
		fc_node->chgParam = NULL;
	}

	switch (nodeTag(fc_node))
	{
			/*
			 * 控制节点
			 */
		case T_ResultState:
			ExecEndResult((ResultState *) fc_node);
			break;

		case T_ProjectSetState:
			ExecEndProjectSet((ProjectSetState *) fc_node);
			break;

		case T_ModifyTableState:
			ExecEndModifyTable((ModifyTableState *) fc_node);
			break;

		case T_AppendState:
			ExecEndAppend((AppendState *) fc_node);
			break;

		case T_MergeAppendState:
			ExecEndMergeAppend((MergeAppendState *) fc_node);
			break;

		case T_RecursiveUnionState:
			ExecEndRecursiveUnion((RecursiveUnionState *) fc_node);
			break;

		case T_BitmapAndState:
			ExecEndBitmapAnd((BitmapAndState *) fc_node);
			break;

		case T_BitmapOrState:
			ExecEndBitmapOr((BitmapOrState *) fc_node);
			break;

			/*
			 * 扫描节点
			 */
		case T_SeqScanState:
			ExecEndSeqScan((SeqScanState *) fc_node);
			break;

		case T_SampleScanState:
			ExecEndSampleScan((SampleScanState *) fc_node);
			break;

		case T_GatherState:
			ExecEndGather((GatherState *) fc_node);
			break;

		case T_GatherMergeState:
			ExecEndGatherMerge((GatherMergeState *) fc_node);
			break;

		case T_IndexScanState:
			ExecEndIndexScan((IndexScanState *) fc_node);
			break;

		case T_IndexOnlyScanState:
			ExecEndIndexOnlyScan((IndexOnlyScanState *) fc_node);
			break;

		case T_BitmapIndexScanState:
			ExecEndBitmapIndexScan((BitmapIndexScanState *) fc_node);
			break;

		case T_BitmapHeapScanState:
			ExecEndBitmapHeapScan((BitmapHeapScanState *) fc_node);
			break;

		case T_TidScanState:
			ExecEndTidScan((TidScanState *) fc_node);
			break;

		case T_TidRangeScanState:
			ExecEndTidRangeScan((TidRangeScanState *) fc_node);
			break;

		case T_SubqueryScanState:
			ExecEndSubqueryScan((SubqueryScanState *) fc_node);
			break;

		case T_FunctionScanState:
			ExecEndFunctionScan((FunctionScanState *) fc_node);
			break;

		case T_TableFuncScanState:
			ExecEndTableFuncScan((TableFuncScanState *) fc_node);
			break;

		case T_ValuesScanState:
			ExecEndValuesScan((ValuesScanState *) fc_node);
			break;

		case T_CteScanState:
			ExecEndCteScan((CteScanState *) fc_node);
			break;

		case T_NamedTuplestoreScanState:
			ExecEndNamedTuplestoreScan((NamedTuplestoreScanState *) fc_node);
			break;

		case T_WorkTableScanState:
			ExecEndWorkTableScan((WorkTableScanState *) fc_node);
			break;

		case T_ForeignScanState:
			ExecEndForeignScan((ForeignScanState *) fc_node);
			break;

		case T_CustomScanState:
			ExecEndCustomScan((CustomScanState *) fc_node);
			break;

			/*
			 * 连接节点
			 */
		case T_NestLoopState:
			ExecEndNestLoop((NestLoopState *) fc_node);
			break;

		case T_MergeJoinState:
			ExecEndMergeJoin((MergeJoinState *) fc_node);
			break;

		case T_HashJoinState:
			ExecEndHashJoin((HashJoinState *) fc_node);
			break;

			/*
			 * 材料化节点
			 */
		case T_MaterialState:
			ExecEndMaterial((MaterialState *) fc_node);
			break;

		case T_SortState:
			ExecEndSort((SortState *) fc_node);
			break;

		case T_IncrementalSortState:
			ExecEndIncrementalSort((IncrementalSortState *) fc_node);
			break;

		case T_MemoizeState:
			ExecEndMemoize((MemoizeState *) fc_node);
			break;

		case T_GroupState:
			ExecEndGroup((GroupState *) fc_node);
			break;

		case T_AggState:
			ExecEndAgg((AggState *) fc_node);
			break;

		case T_WindowAggState:
			ExecEndWindowAgg((WindowAggState *) fc_node);
			break;

		case T_UniqueState:
			ExecEndUnique((UniqueState *) fc_node);
			break;

		case T_HashState:
			ExecEndHash((HashState *) fc_node);
			break;

		case T_SetOpState:
			ExecEndSetOp((SetOpState *) fc_node);
			break;

		case T_LockRowsState:
			ExecEndLockRows((LockRowsState *) fc_node);
			break;

		case T_LimitState:
			ExecEndLimit((LimitState *) fc_node);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			break;
	}
}

/*
 * ExecShutdownNode
 *
 * 给予执行节点停止异步资源消耗
 * 的机会，并释放仍然持有的任何资源。
 */
bool ExecShutdownNode(PlanState *fc_node)
{
	return fc_ExecShutdownNode_walker(fc_node, NULL);
}

static bool fc_ExecShutdownNode_walker(PlanState *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;

	check_stack_depth();

	/*
	 * 在关闭节点时将其视为正在运行，但前提是
	 * 它已经至少运行过一次。我们不期望在节点
	 * 关闭期间有太多 CPU 消耗，但在 Gather 或 
	 * Gather Merge 的情况下，我们可能会在这个阶段关闭
	 * 工作线程。如果是这样，工作线程的缓冲区使用
	 * 将在此处传播到 pgBufferUsage，我们希望确保
	 * 这与 Gather 节点相关联。如果节点从未执行，
	 * 我们会跳过此步骤，以避免错误地使其显示为已执行。
	 */
	if (fc_node->instrument && fc_node->instrument->running)
		InstrStartNode(fc_node->instrument);

	planstate_tree_walker(fc_node, fc_ExecShutdownNode_walker, fc_context);

	switch (nodeTag(fc_node))
	{
		case T_GatherState:
			ExecShutdownGather((GatherState *) fc_node);
			break;
		case T_ForeignScanState:
			ExecShutdownForeignScan((ForeignScanState *) fc_node);
			break;
		case T_CustomScanState:
			ExecShutdownCustomScan((CustomScanState *) fc_node);
			break;
		case T_GatherMergeState:
			ExecShutdownGatherMerge((GatherMergeState *) fc_node);
			break;
		case T_HashState:
			ExecShutdownHash((HashState *) fc_node);
			break;
		case T_HashJoinState:
			ExecShutdownHashJoin((HashJoinState *) fc_node);
			break;
		default:
			break;
	}

	/* 如果我们在上面启动了节点，则停止它，并报告 0 个元组。 */
	if (fc_node->instrument && fc_node->instrument->running)
		InstrStopNode(fc_node->instrument, 0);

	return false;
}

/*
 * ExecSetTupleBound
 *
 * 为计划状态节点设置元组边界。这让子计划节点能够基于父节点所需的最大元组数量是有限的知识进行优化。节点的元组边界只能在扫描之间更改（即，在节点初始化后或在 ExecReScan 调用之前）。
 *
 * 任何负的 tuples_needed 值都意味着“没有限制”，这是对特定节点没有调用时的默认假设。
 *
 * 注意：如果在计划树上重复调用此方法，则每次都必须用新限制更新完全相同的节点集；务必确保这里只测试不变的条件。
 */
void ExecSetTupleBound(int64 fc_tuples_needed, PlanState *fc_child_node)
{
	/*
	 * 由于此函数采用递归调用，从原则上来说，我们应该在这里检查堆栈深度。
	 * 在实践中，这可能毫无意义，因为较早的节点初始化树遍历肯定会消耗更多堆栈。
	 */

	if (IsA(fc_child_node, SortState))
	{
		/*
		 * 如果这是一个排序节点，通知它可以使用有界排序。
		 *
		 * 注意：nodeSort.c 负责对这些参数的变化做出正确反应。
		 * 如果我们重新设计这部分，将此信号与参数变化机制集成是一种不错的主意。
		 */
		SortState  *fc_sortState = (SortState *) fc_child_node;

		if (fc_tuples_needed < 0)
		{
			/* 确保在重新扫描时如有需要重置标志 */
			fc_sortState->bounded = false;
		}
		else
		{
			fc_sortState->bounded = true;
			fc_sortState->bound = fc_tuples_needed;
		}
	}
	else if (IsA(fc_child_node, IncrementalSortState))
	{
		/*
		 * 如果这是一个增量排序节点，通知它可以使用有界排序。
		 *
		 * 注意：nodeIncrementalSort.c 负责对这些参数的变化做出正确反应。
		 * 如果我们重新设计这部分，将此信号与参数变化机制集成是一种不错的主意。
		 */
		IncrementalSortState *fc_sortState = (IncrementalSortState *) fc_child_node;

		if (fc_tuples_needed < 0)
		{
			/* 确保在重新扫描时如有需要重置标志 */
			fc_sortState->bounded = false;
		}
		else
		{
			fc_sortState->bounded = true;
			fc_sortState->bound = fc_tuples_needed;
		}
	}
	else if (IsA(fc_child_node, AppendState))
	{
		/*
		 * 如果这是一个附加节点，我们可以将边界应用于附加节点的任何子节点，因为附加节点无疑不会从任何一个输入中读取超过那么多的元组。
		 */
		AppendState *fc_aState = (AppendState *) fc_child_node;
		int			fc_i;

		for (fc_i = 0; fc_i < fc_aState->as_nplans; fc_i++)
			ExecSetTupleBound(fc_tuples_needed, fc_aState->appendplans[fc_i]);
	}
	else if (IsA(fc_child_node, MergeAppendState))
	{
		/*
		 * 如果这是一个合并附加节点，我们可以将边界应用于合并附加节点的任何子节点，因为合并附加节点无疑不会从任何一个输入中读取超过那么多的元组。
		 */
		MergeAppendState *fc_maState = (MergeAppendState *) fc_child_node;
		int			fc_i;

		for (fc_i = 0; fc_i < fc_maState->ms_nplans; fc_i++)
			ExecSetTupleBound(fc_tuples_needed, fc_maState->mergeplans[fc_i]);
	}
	else if (IsA(fc_child_node, ResultState))
	{
		/*
		 * 同样，对于一个投影结果，我们可以将边界应用于它的子节点。
		 *
		 * 如果结果支持条件检查，我们将不得不在看到条件时放弃。
		 * 注意，拥有一个 resconstantqual 并不是一个阻碍条件：如果该条件成功，则不会影响任何东西，而如果失败，结果子节点无论如何不会要求任何行。
		 */
		if (outerPlanState(fc_child_node))
			ExecSetTupleBound(fc_tuples_needed, outerPlanState(fc_child_node));
	}
	else if (IsA(fc_child_node, SubqueryScanState))
	{
		/*
		 * 我们也可以通过子查询扫描进行深入，但前提是它没有条件（否则可能会丢弃行）。
		 */
		SubqueryScanState *fc_subqueryState = (SubqueryScanState *) fc_child_node;

		if (fc_subqueryState->ss.ps.qual == NULL)
			ExecSetTupleBound(fc_tuples_needed, fc_subqueryState->subplan);
	}
	else if (IsA(fc_child_node, GatherState))
	{
		/*
		 * 收集节点可以将边界传递给其工作线程。与合并附加相同，没有任何工作线程可能需要返回比收集本身需要更多的元组。
		 *
		 * 注意：与排序一样，收集节点负责对该参数的变化做出正确反应。
		 */
		GatherState *fc_gstate = (GatherState *) fc_child_node;

		fc_gstate->tuples_needed = fc_tuples_needed;

		/* 还将边界传递到我们自己的子计划副本 */
		ExecSetTupleBound(fc_tuples_needed, outerPlanState(fc_child_node));
	}
	else if (IsA(fc_child_node, GatherMergeState))
	{
		/* 与收集相同的评论 */
		GatherMergeState *fc_gstate = (GatherMergeState *) fc_child_node;

		fc_gstate->tuples_needed = fc_tuples_needed;

		ExecSetTupleBound(fc_tuples_needed, outerPlanState(fc_child_node));
	}

	/*
	 * 从原则上说，我们可以通过任何肯定不会丢弃或组合输入行的计划节点类型，但在看到可以这样做的节点时，我们就无法再传递边界。
	 * 目前尚不清楚是否还有其他情况值得在这里检查。
	 */
}
